package com.lemon.boot.common.config;

import cn.hutool.core.util.RandomUtil;
import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties;
import com.lemon.boot.common.consts.RedisKeyConst;
import com.lemon.boot.common.exception.CustomException;
import com.lemon.boot.common.filter.LoginAuthenticationFilter;
import com.lemon.boot.common.filter.RequestBodyRepeatReadFilter;
import com.lemon.boot.common.filter.TokenAuthenticationFilter;
import com.lemon.boot.common.listener.ApplicationStartEventListener;
import com.lemon.boot.common.properties.JwtConfigProperties;
import com.lemon.boot.common.properties.RememberMeConfigProperties;
import com.lemon.boot.common.service.ElasticsearchService;
import com.lemon.boot.common.service.RedisService;
import com.lemon.boot.common.service.impl.RememberMeServiceImpl;
import com.lemon.boot.common.utils.JwtUtil;
import com.lemon.boot.common.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.*;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;

/**
 * @author 李猛
 * @datetime 2024/2/7 23:31
 * @description security配置
 */
@Slf4j
@EnableWebSecurity
@EnableRedisHttpSession //启用RedisHttpSession
@AutoConfigureBefore({
        UserDetailsService.class,
        RedisService.class,
        RememberMeServiceImpl.class,
        ElasticsearchService.class,
        ApplicationStartEventListener.class,
        JwtConfigProperties.class,
        Knife4jProperties.class,
        RememberMeConfigProperties.class
})
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true, proxyTargetClass = true)
public class SecurityConfiguration {
    /**
     * security 配置
     *
     * @param authenticationManager
     * @param http
     * @param redisService
     * @param securityRememberMeService
     * @param elasticsearchService
     * @param applicationStartEventListener
     * @param jwtConfigProperties
     * @param rememberMeConfigProperties
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(AuthenticationManager authenticationManager,
                                                   HttpSecurity http,
                                                   RedisService redisService,
                                                   RememberMeServiceImpl securityRememberMeService,
                                                   ElasticsearchService elasticsearchService,
                                                   ApplicationStartEventListener applicationStartEventListener,
                                                   JwtConfigProperties jwtConfigProperties,
                                                   RememberMeConfigProperties rememberMeConfigProperties) throws Exception {
        return http
                //自定义登录
                .formLogin(formLoginCustomizer())
                //配置请求权限
                .authorizeHttpRequests(authorizeHttpRequestsConfigurer(jwtConfigProperties, applicationStartEventListener))
                //请求缓存
                .requestCache(requestCacheCustomizer())
                //上下文
                .securityContext(securityContextCustomizer())
                //配置跨站请求伪造（CSRF）防护
                .csrf(csrfCustomizer(jwtConfigProperties, applicationStartEventListener))
                //配置跨域
                .cors(corsConfigurer(jwtConfigProperties))
                //配置请求头
                .headers(headersConfigurer())
                //记住我配置
                .rememberMe(rememberMeCustomizer(securityRememberMeService))
                //登出配置
                .logout(logoutConfigurer(redisService))
                //异常配置
                .exceptionHandling(exceptionHandlingConfigurer())
                //session配置
                .sessionManagement(sessionManagementConfigurer(sessionRegistry()))
                //登录过滤器
                .addFilter(new LoginAuthenticationFilter(authenticationManager, redisService, securityRememberMeService, jwtConfigProperties, rememberMeConfigProperties))
                //Token认证及续签过滤器
                .addFilterAfter(new TokenAuthenticationFilter(authenticationManager, redisService, elasticsearchService, jwtConfigProperties), LoginAuthenticationFilter.class)
                //请求体过滤器
                .addFilterBefore(new RequestBodyRepeatReadFilter(), TokenAuthenticationFilter.class)
                //构建
                .build();
    }

    /**
     * 配置认证管理器
     *
     * @param http
     * @param userDetailsService
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
        return http
                .getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder())
                .and()
                .build();
    }

    /**
     * 密码加密
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * session配置
     *
     * @return
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    /**
     * session配置
     *
     * @return
     */
    @Bean
    public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(sessionRegistry());
    }

    /**
     * 自定义登录配置
     *
     * @return
     */
    private static Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer() {
        return AbstractHttpConfigurer::disable;
    }

    /**
     * 配置请求权限
     *
     * @return
     */
    private static Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequestsConfigurer(JwtConfigProperties jwtConfigProperties, ApplicationStartEventListener applicationStartEventListener) {
        return http -> http.mvcMatchers(jwtConfigProperties.getPassStaticUri()).permitAll()//放行静态资源
                .antMatchers(jwtConfigProperties.getPassUri()).permitAll()//放行接口
                .antMatchers(applicationStartEventListener.getNotAuthorizationPath()).permitAll()//不需要认证的接口
                .anyRequest().authenticated();
    }

    /**
     * 配置请求缓存
     *
     * @return
     */
    private static Customizer<RequestCacheConfigurer<HttpSecurity>> requestCacheCustomizer() {
        return AbstractHttpConfigurer::disable;
    }

    /**
     * 安全上下文
     *
     * @return
     */
    private static Customizer<SecurityContextConfigurer<HttpSecurity>> securityContextCustomizer() {
        return AbstractHttpConfigurer::disable;
    }

    /**
     * 配置跨站请求伪造（CSRF）防护
     *
     * @param jwtConfigProperties
     * @param applicationStartEventListener
     * @return
     */
    private static Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer(JwtConfigProperties jwtConfigProperties, ApplicationStartEventListener applicationStartEventListener) {
        return csrf -> {
            try {
                csrf
                        .ignoringAntMatchers(jwtConfigProperties.getPassUri())
                        .ignoringAntMatchers(applicationStartEventListener.getNotAuthorizationPath())
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .and()
                        .headers(header -> header
                                .addHeaderWriter((request, response) -> response
                                        .setHeader("x-csrf-token", String.format("lemon-system-%s", RandomUtil.randomString(32)))));
            } catch (Exception e) {
                throw new CustomException(e.getMessage());
            }
        };
    }

    /**
     * 跨域配置
     *
     * @param jwtConfigProperties
     * @return
     */
    private static Customizer<CorsConfigurer<HttpSecurity>> corsConfigurer(JwtConfigProperties jwtConfigProperties) {
        return cors -> cors.configurationSource(request -> {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.addAllowedOriginPattern("*");
            configuration.addAllowedHeader("*");
            configuration.setAllowedMethods(jwtConfigProperties.getAllowMethodByList());
            configuration.setAllowCredentials(true);
            configuration.setMaxAge(3600L);
            return configuration;
        });
    }

    /**
     * 配置请求头
     *
     * @return
     */
    private static Customizer<HeadersConfigurer<HttpSecurity>> headersConfigurer() {
        return headers -> headers
                //禁用缓存
                .cacheControl(HeadersConfigurer.CacheControlConfig::disable)
                //允许跨域
                .frameOptions().sameOrigin();
    }

    /**
     * 记住我 配置
     *
     * @return
     */
    private static Customizer<RememberMeConfigurer<HttpSecurity>> rememberMeCustomizer(RememberMeServices rememberMeServices) {
        return rememberMe -> rememberMe.rememberMeServices(rememberMeServices);
    }

    /**
     * 退出配置
     *
     * @param redisService
     * @return
     */
    private static Customizer<LogoutConfigurer<HttpSecurity>> logoutConfigurer(RedisService redisService) {
        return logout -> logout//退出配置
                .invalidateHttpSession(true)//退出时，让session失效
                .clearAuthentication(true)//清除认证信息
                .deleteCookies("JSESSIONID")//清除cookie
                .logoutSuccessHandler((request, response, authentication) -> {
                    log.info("执行退出操作");
                    String token = JwtUtil.getToken(request);
                    if (StringUtils.hasText(token)) {
                        //删除redis里的数据
                        String loginUserKey = String.format("%s%s", RedisKeyConst.REDIS_KEY_LOGIN_USER, JwtUtil.getCurrentUser(token).getUsername());
                        redisService.del(loginUserKey);
                        ResponseUtil.success(response, "退出成功！");
                    } else {
                        ResponseUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), "请先登录！");
                    }
                });
    }


    /**
     * 异常配置
     *
     * @return
     */
    private static Customizer<ExceptionHandlingConfigurer<HttpSecurity>> exceptionHandlingConfigurer() {
        return exceptionHandling -> exceptionHandling
                //认证失败-无权限访问
                .accessDeniedHandler((request, response, exception) -> {
                    log.error("权限访问被拒绝，也就是未授权访问的接口:{}，message:{}", request.getRequestURI(), exception.getMessage());
                    ResponseUtil.fail(response, HttpStatus.FORBIDDEN.value(), "未授权操作！");
                })
                //认证失败-未登录
                .authenticationEntryPoint((request, response, exception) -> {
                    log.error("认证失败，未登录的操作，message:{}", exception.getMessage());
                    ResponseUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), "请先登录！");
                });
    }

    /**
     * session配置
     *
     * @param sessionRegistry
     * @return
     */
    private static Customizer<SessionManagementConfigurer<HttpSecurity>> sessionManagementConfigurer(SessionRegistry sessionRegistry) {
        return sessionManagement -> sessionManagement
                //禁用session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //不允许多地登录-设置只有一个会话
                .maximumSessions(1)
                //默认是false，只允许第一个人登录
                .maxSessionsPreventsLogin(false)
                //配置sessionRegistry session 共享
                .sessionRegistry(sessionRegistry);
    }
}
